package com.chinawayltd.ews.sample.taskCenter.distributedLock;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCommands;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;

/**
 * @program: greeProject.timingTaskCenter
 * @description:
 * @author: xiazj
 * @create: 2018-06-04 16:10
 **/
@Service
public class DistributedLockManagerRedisImpl implements DistributedLockManager {

    private static final String LOCK_PREFIX = "LOCK:";
    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "EX";

    private static final String UNLOCK_LUA;
    private static final String DELAY_UNLOCK_LUA;
    private static final String IP;
    static {
        StringBuilder sb = new StringBuilder();
        sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
        sb.append("then ");
        sb.append("    return redis.call(\"del\",KEYS[1]) ");
        sb.append("else ");
        sb.append("    return 0 ");
        sb.append("end ");
        UNLOCK_LUA = sb.toString();

        sb = new StringBuilder();
        sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
        sb.append("then ");
        sb.append("    return redis.call(\"expire\",KEYS[1], 1)");
        sb.append("else ");
        sb.append("    return 0 ");
        sb.append("end ");
        DELAY_UNLOCK_LUA = sb.toString();

        String tempIp = "";
        try {
            tempIp = InetAddress.getLocalHost().getHostAddress().toString();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        IP = tempIp;
    }

    @Value("${system.appId}")
    private String appName;
    @Autowired
    private RedisTemplate redisTemplate;

    private String getRequestId(){
        return IP + "::" + appName;
    }

    @Override
    public boolean tryLock(String lockKey, String requestId) {
        return this.tryLock(lockKey, requestId, DEF_EXPIRE_TIME);
    }

    @Override
    public boolean tryLock(String lockKey, int expireTime) {
        return this.tryLock(lockKey, getRequestId(), expireTime);
    }

    @Override
    public boolean tryLock(String lockKey) {
        return this.tryLock(lockKey, DEF_EXPIRE_TIME);
    }

    @Override
    public boolean tryLock(String lockKey,String requestId,int expireTime) {

        Object result = redisTemplate.execute(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                JedisCommands commands = (JedisCommands) connection.getNativeConnection();
                return commands.set(LOCK_PREFIX + lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
            }
        });

        if (result != null && LOCK_SUCCESS.equals(result.toString())) {
            return true;
        }
        return false;
    }

    private boolean releaseLock(String lockKey, String requestId, String scriptStr) {
        List<String> keys = new ArrayList<>();
        keys.add(LOCK_PREFIX + lockKey);
        List<String> args = new ArrayList<>();
        args.add(requestId);

        Object result = redisTemplate.execute(new RedisCallback<Object>() {
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                Object nativeConnection = connection.getNativeConnection();
                if (nativeConnection instanceof Jedis) {
                    return ((Jedis) nativeConnection).eval(scriptStr, keys, args);
                }
                return null;
            }
        });
        if(result != null){
            Long resultObj = (Long)result;
            return resultObj > 0;
        }
        return false;
    }

    @Override
    public boolean releaseLock(String lockKey, String requestId) {
        return this.releaseLock(lockKey, requestId, UNLOCK_LUA);
    }

    @Override
    public boolean releaseLock(String lockKey) {
        return this.releaseLock(lockKey, getRequestId());
    }

    @Override
    public boolean releaseLockDelay(String lockKey, String requestId) {
        return this.releaseLock(lockKey, requestId, DELAY_UNLOCK_LUA);
    }

    @Override
    public boolean releaseLockDelay(String lockKey) {
        return this.releaseLockDelay(lockKey, getRequestId());
    }

}
